package wsService

import (
	"Student_Mental/DBconfing"
	"Student_Mental/controller/login"
	_ "Student_Mental/controller/login"
	"context"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"
)

// Message is return msg
type Message struct {
	RoomId        string `json:"roomId,omitempty"`
	Sender        string `json:"sender,omitempty"`
	SenderName    string `json:"senderName,omitempty"`
	Recipient     string `json:"recipient,omitempty"`
	Content       string `json:"content,omitempty"`
	SendTime      string `json:"sendTime,omitempty"`
	Type          string `json:"type,omitempty"`
	SendTimestamp string `json:"SendTimestamp,omitempty"`
}

var (
	cache = &sync.Pool{
		New: func() interface{} {
			return &Message{}
		},
	}
)

func GetOption() *Message {
	return cache.Get().(*Message)
}
func ReleaseOption(opt *Message) {
	opt.reset()
	cache.Put(opt) //在使用完对象后，可以使用Put将其放回对象池中
}
func (m *Message) reset() {
	m.RoomId = ""
	m.Sender = ""
	m.SenderName = ""
	m.Recipient = ""
	m.Content = ""
	m.SendTime = ""
	m.Type = ""
}

var Up = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	// 解决跨域问题
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

type Client struct {
	ID     string
	Socket *websocket.Conn
	Send   chan []byte
}

// ClientManager is a websocket manager
type ClientManager struct {
	Clients    map[string]*Client
	Broadcast  chan []byte
	Register   chan *Client
	Unregister chan *Client
}

// Manager define a ws server manager
var Manager = ClientManager{
	Broadcast:  make(chan []byte),
	Register:   make(chan *Client),
	Unregister: make(chan *Client),
	Clients:    make(map[string]*Client),
}
var constId string
var cancelFuncs = make(map[string]context.CancelFunc)
var WithCancels = make(map[string]context.Context)
var Online []string //在线人数的统计

// Start is  项目运行前, 协程开启start -> go Manager.Start()
func (manager *ClientManager) Start() {
	red, err := DBconfing.ConnRedis()
	if err != nil {
		for {
			time.Sleep(time.Second) // 不断地等待1秒钟，并尝试重新连接Redis服务器，直到连接成功。
			red, err = DBconfing.ConnRedis()
			if err == nil {
				break
			}
		}
	}
	defer red.Close()
	for {
		log.Println("<---管道通信--->")
		select {
		case conn := <-Manager.Register:
			log.Printf("新用户加入:%v", conn.ID)
			Manager.Clients[conn.ID] = conn
			jsonMessage, _ := json.Marshal(&Message{Content: "Successful connection to socket service", Type: "login"})
			conn.Send <- jsonMessage
			joinOnline(&Online, conn)
		case conn := <-Manager.Unregister:
			log.Printf("用户离开:%v", conn.ID)
			if _, ok := Manager.Clients[conn.ID]; ok {
				jsonMessage, _ := json.Marshal(&Message{Content: "A socket has disconnected", Type: "exit"})
				conn.Send <- jsonMessage
				go offline(&Online, conn.ID)
				close(conn.Send)
				delete(Manager.Clients, conn.ID)
			}
		case message := <-Manager.Broadcast:
			MessageStruct := Message{}
			json.Unmarshal(message, &MessageStruct)
			//写入redis  MessageStruct.RoomId
			//做房间聊天消息的存储
			if MessageStruct.Type == "msg" || MessageStruct.Type == "pic" || MessageStruct.Type == "booking" {
				red.LPush(MessageStruct.RoomId, message)
				red.Do("EXPIRE", MessageStruct.RoomId, 7200) //设置过期时间
				red.LPush("messageList", message)

				// 发送消息到客户端
				for Id, conn := range Manager.Clients {
					if Id != MessageStruct.Recipient && Id != MessageStruct.Sender {
						continue
					}
					select {
					case conn.Send <- message:
					default:
						close(conn.Send)
						delete(Manager.Clients, conn.ID)
					}
				}
				//判断对面是否在线
				if _, ok := Manager.Clients[MessageStruct.Recipient]; !ok {
					//未读消息
					floatSender, _ := strconv.ParseFloat(MessageStruct.Sender, 64) //转型
					MessageStruct.Type = "Unread"
					jsonMessage, _ := json.Marshal(MessageStruct)
					red.ZAdd("Unread"+MessageStruct.Recipient, redis.Z{Score: floatSender, Member: jsonMessage}) //存入redis 的 zSet
					continue
				}
			} else if MessageStruct.Type == "Unread" { //做未读消息存储
				//未读消息
				floatSender, _ := strconv.ParseFloat(MessageStruct.Sender, 64)                           //转型
				red.ZAdd("Unread"+MessageStruct.Recipient, redis.Z{Score: floatSender, Member: message}) //存入redis 的 zSet
				//red.LPush("Unread"+MessageStruct.Recipient, message)   //废弃之前的list存储
			}
		}
	}
}

var client *Client

func Handler(c *gin.Context) {
	auth := c.Query("JWT")
	_, claims1, _ := login.ParseToken(auth) //解析token
	constId = claims1.Id
	println("发送人==============" + constId)
	conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		http.NotFound(c.Writer, c.Request)
		return
	}
	log.Printf("服务连接")
	//可以添加用户信息验证
	client = &Client{
		ID:     constId,
		Socket: conn,
		Send:   make(chan []byte),
	}
	Manager.Register <- client
	go client.Read()
	go client.Write()
}

func (c *Client) Read() {
	defer func() {
		Manager.Unregister <- c
		c.Socket.Close()
	}()
	for {
		c.Socket.PongHandler()
		_, message, err := c.Socket.ReadMessage()
		if err != nil {
			Manager.Unregister <- c
			c.Socket.Close()
			break
		}
		log.Printf("读取到客户端的信息:%s", string(message))
		Manager.Broadcast <- message
	}
}
func (c *Client) Write() {
	defer func() {
		c.Socket.Close()
	}()
	for {
		select {
		case message, ok := <-c.Send:
			if !ok {
				c.Socket.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			log.Printf("发送到到客户端的信息:%s", string(message))
			c.Socket.WriteMessage(websocket.TextMessage, message)
		}
	}
}

func joinOnline(Online *[]string, conn *Client) {
	for _, number := range *Online {
		if number == conn.ID {
			return
		}
	}
	jsonMessage, _ := json.Marshal(&Message{Content: conn.ID, Type: "login"})
	sendAllConn(jsonMessage)
	*Online = append(*Online, conn.ID)                                  //加入在线切片
	ctx := context.WithValue(context.Background(), "trace_id", conn.ID) // 使用连接的ID作为trace_id创建一个上下文ctx
	WithCancel, cancelFunc := context.WithCancel(ctx)                   // 创建一个新的上下文WithCancel，该上下文继承ctx的值，用于取消操作
	cancelFuncs[conn.ID] = cancelFunc                                   // 将该连接的取消函数存储到一个映射表cancelFuncs中，以便以后可以通过连接ID进行查找和调用
	WithCancels[conn.ID] = WithCancel                                   // 将该连接的WithCancel上下文存储到一个映射表WithCancels中，以便以后可以通过连接ID进行查找和使用
}

var mu sync.Mutex

func offline(Online *[]string, Id string) {
	// 加锁
	mu.Lock()
	defer mu.Unlock()

	ctx := WithCancels[Id]
	cancelFunc := cancelFuncs[Id]

	// 检查 context 是否已经被取消
	select {
	case <-ctx.Done():
		// 如果 context 被取消，则直接返回
		return
	default:
		// 否则执行函数体中的代码
		time.Sleep(5 * time.Second)
		conn := Manager.Clients[Id]
		if conn == nil { //等待了10秒如果还是空，就表示用户已经退出
			// 发送消息到客户端
			jsonMessage, _ := json.Marshal(&Message{Content: Id, Type: "exit"})
			cancelFunc() //关闭ctx
			sendAllConn(jsonMessage)
			// 找到要删除的元素所在的索引
			index := -1
			for i, v := range *Online {
				if v == Id {
					index = i
					break
				}
			}

			// 如果找到了要删除的元素，则删除它
			if index >= 0 {
				*Online = append((*Online)[:index], (*Online)[index+1:]...)
			}
		}
	}
}

//发送给所有在线的用户
func sendAllConn(jsonMessage []byte) {
	for _, connAll := range Manager.Clients {
		fmt.Println(connAll)
		select {
		case connAll.Send <- jsonMessage:
		default:
			close(connAll.Send)
			delete(Manager.Clients, connAll.ID)
		}
	}
}

// ReturnOnline 返回在线人数
func ReturnOnline(c *gin.Context) {
	c.JSON(200, gin.H{
		"OnlineList": Online,
	})
}
